package com.kool.kadmin.common.coding.codegen;

import com.kool.kadmin.common.coding.codegen.vo.ColumnMetaVo;
import com.kool.kadmin.common.constant.ErrorCode;
import com.kool.kadmin.common.exception.AppException;
import com.kool.kadmin.common.util.FileUtil;
import com.kool.kadmin.common.util.PropertyUtil;
import com.kool.kadmin.common.util.StringUtil;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @author tianze
 * @since 2021/8/13 10:15
 */
@Slf4j
public class CodeGenTool {

    public static void main(String[] args) {
        String url = PropertyUtil.getProp("spring.datasource.url");
        String user = PropertyUtil.getProp("spring.datasource.username");
        String password = PropertyUtil.getProp("spring.datasource.password");
        String pkg = "com.kool.pangu.platform.*";
        String tables = "pl_user";
        String toDir = "./target/code/";
        CodeGenTool.generate(pkg, tables, toDir, url, user, password);
    }

    /**
     * 生成代码
     * Do.java
     * Dao.java     * Dao.xml
     * @param pkg
     * @param tables
     * @param toDir
     * @param url
     * @param username
     * @param password
     */
    public static void generate(String pkg, String tables, String toDir, String url, String username, String password) {
        if (StringUtil.isEmpty(pkg) || StringUtil.isEmpty(tables) || StringUtil.isEmpty(toDir)) {
            throw new RuntimeException("参数不能为空");
        }
        Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        try {
            configuration.setClassForTemplateLoading(CodeGenTool.class, "/template");
            configuration.setDefaultEncoding("UTF-8");
            configuration.setObjectWrapper(new DefaultObjectWrapper(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS));
        } catch (Exception e) {
            throw new AppException(ErrorCode.SYSTEM_ERROR, e, StringUtil.getUUID());
        }

        String tableSchema = url.split("\\?")[0];
        tableSchema = tableSchema.substring(tableSchema.lastIndexOf("/") + 1);


        String[] listTable = tables.split(",");
        toDir = toDir + System.currentTimeMillis();
        log.debug("生成文件路径：" + toDir);

        Connection connection = getConnection(url, username, password);
        try {
            ColumnsDao dao = new ColumnsDao(connection);
            for (String table : listTable) {
                // 根据表名，读取表结构
                ColumnMetaVo condition = new ColumnMetaVo();
                condition.setTableName(table);
                condition.setTableSchema(tableSchema);
                List<ColumnMetaVo> listRet = dao.select(condition);
                if (listRet == null || listRet.size() == 0) {
                    log.info("{}表不存在", table);
                    throw new AppException(ErrorCode.SYSTEM_ERROR, "【" + table + "】表不存在");
                }
                // 获取或创建一个模版。
                Template template = configuration.getTemplate("DAO.ftl");
                template.setOutputEncoding("UTF-8");
                // 生成文件到目标文件夹下
                HashMap<String, Object> map = getDaoMap(table, pkg, listRet);
                Writer writer = getDaoWriter(table, getDaoDir(toDir, pkg));
                template.process(map, writer);
                writer.flush();
                writer.close();

                template = configuration.getTemplate("DO.ftl");
                template.setOutputEncoding("UTF-8");
                map = getBeanMap(table, pkg, listRet);
                writer = getBeanWriter(table, getBeanDir(toDir, pkg));
                template.process(map, writer);
                writer.flush();
                writer.close();

                template = configuration.getTemplate("Xml.ftl");
                template.setOutputEncoding("UTF-8");
                map = getXmlMap(table, pkg, listRet);
                writer = getXmlWriter(table, getXmlDir(toDir, pkg));
                template.process(map, writer);
                writer.flush();
                writer.close();
            }
            log.debug("完成");
        } catch (IOException | TemplateException e) {
            throw new AppException(ErrorCode.SYSTEM_ERROR, e, StringUtil.getUUID());
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * 将数据库字段类型转为java类型
     *
     * @param dataType
     * @return
     */
    public static String transferType(String dataType) {
        String type = null;
        if ("VARCHAR".equalsIgnoreCase(dataType)
                || ("CHAR".equalsIgnoreCase(dataType))
                || ("TEXT".equalsIgnoreCase(dataType))) {
            type = "String";
        } else if ("INT".equalsIgnoreCase(dataType)) {
            type = "Integer";
        } else if ("TINYINT".equalsIgnoreCase(dataType)) {
            type = "Integer";
        } else if ("DOUBLE".equalsIgnoreCase(dataType)) {
            type = "Double";
        } else if ("DATE".equalsIgnoreCase(dataType)) {
            type = "java.util.Date";
        } else if ("TIMESTAMP".equalsIgnoreCase(dataType)) {
            type = "java.sql.Timestamp";
        } else if ("DECIMAL".equalsIgnoreCase(dataType)) {
            type = "java.math.BigDecimal";
        } else if ("BIGINT".equalsIgnoreCase(dataType)) {
            type = "Long";
        } else if ("DATETIME".equalsIgnoreCase(dataType)) {
            type = "java.util.Date";
        } else {
            type = "Object";
        }
        return type;
    }

    private static Writer getXmlWriter(String table, String xmlDir) throws IOException {
        String fileName = StringUtil.transferToHumpName(table, true);
        fileName = "I" + fileName + "DAO.xml";
        String destPath = xmlDir + FileUtil.getSeparator() + fileName;
        return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(destPath), StandardCharsets.UTF_8));
    }

    private static Writer getBeanWriter(String table, String beanDir) throws IOException {
        String fileName = StringUtil.transferToHumpName(table, true);
        fileName = fileName + "DO.java";
        String destPath = beanDir + FileUtil.getSeparator() + fileName;
        return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(destPath), StandardCharsets.UTF_8));
    }

    private static Writer getDaoWriter(String table, String daoDir) throws IOException {
        String fileName = StringUtil.transferToHumpName(table, true);
        fileName = "I" + fileName + "DAO.java";
        String destPath = daoDir + FileUtil.getSeparator() + fileName;
        return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(destPath), StandardCharsets.UTF_8));
    }

    private static String getXmlDir(String toDir, String pkg) {
        String dir = toDir + FileUtil.getSeparator() + "mybatis";
        File fDir = new File(dir);
        if (!fDir.exists()) {
            fDir.mkdirs();
        }
        return dir;
    }

    private static String getBeanDir(String toDir, String pkg) {
        String dir = toDir + FileUtil.getSeparator() + pkg.replace('.', '/').replace("*", "domain");
        File fDir = new File(dir);
        if (!fDir.exists()) {
            fDir.mkdirs();
        }
        return dir;
    }

    private static String getDaoDir(String toDir, String pkg) {
        String dir = toDir + FileUtil.getSeparator() + pkg.replace('.', '/').replace("*", "dao");
        File fDir = new File(dir);
        if (!fDir.exists()) {
            fDir.mkdirs();
        }
        return dir;
    }

    private static HashMap<String, Object> getXmlMap(String table, String pkg, List<ColumnMetaVo> listRet) {
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("doPkg", pkg.replace("*", "domain"));
        map.put("daoPkg", pkg.replace("*", "dao"));
        map.put("upTumTabNam", StringUtil.transferToHumpName(table, true));
        map.put("lowTumTabNam", StringUtil.transferToHumpName(table, false));
        map.put("tabNam", table);

        ArrayList<ColumnMetaVo> list = new ArrayList<>();
        ArrayList<ColumnMetaVo> insertList = new ArrayList<>();
        ArrayList<ColumnMetaVo> pkList = new ArrayList<>();

        boolean hasPk = false;
        for (ColumnMetaVo value : listRet) {
            value.setDataType(value.getDataType().toUpperCase());
            value.setJavaType(transferType(value.getDataType()));
            value.setUpJavaName(StringUtil.transferToHumpName(value.getColumnName(), true));
            value.setLowJavaName(StringUtil.transferToHumpName(value.getColumnName(), false));
            value.setString(value.getJavaType().equalsIgnoreCase("String"));
            list.add(value);

            if ("PRI".equalsIgnoreCase(value.getColumnKey())) {
                hasPk = true;
                pkList.add(value);
            }

            if (!"id".equalsIgnoreCase(value.getColumnName())
                    && !"is_deleted".equalsIgnoreCase(value.getColumnName())
                    && !"gmt_create".equalsIgnoreCase(value.getColumnName())
                    && !"gmt_modified".equalsIgnoreCase(value.getColumnName())) {
                insertList.add(value);
            }
        }
        map.put("columns", list);
        map.put("insertColumns", insertList);
        map.put("hasPk", hasPk);
        map.put("pkList", pkList);
        return map;
    }

    private static HashMap<String, Object> getBeanMap(String table, String pkg, List<ColumnMetaVo> listRet) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("daoPkg", pkg.replace("*", "dao"));
        map.put("doPkg", pkg.replace("*", "domain"));
        map.put("upTumTabNam", StringUtil.transferToHumpName(table, true));
        map.put("lowTumTabNam", StringUtil.transferToHumpName(table, false));
        map.put("tabNam", table);
        ArrayList<ColumnMetaVo> list = new ArrayList<>();
        for (ColumnMetaVo col : listRet) {
            col.setJavaType(transferType(col.getDataType()));
            col.setUpJavaName(StringUtil.transferToHumpName(col.getColumnName(), true));
            col.setLowJavaName(StringUtil.transferToHumpName(col.getColumnName(), false));
            list.add(col);
        }
        map.put("columns", list);
        return map;
    }

    private static HashMap<String, Object> getDaoMap(String table, String pkg, List<ColumnMetaVo> listRet) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("daoPkg", pkg.replace("*", "dao"));
        map.put("doPkg", pkg.replace("*", "domain"));
        map.put("upTumTabNam", StringUtil.transferToHumpName(table, true));
        map.put("lowTumTabNam", StringUtil.transferToHumpName(table, false));

        boolean hasPk = false;
        for (ColumnMetaVo value : listRet) {
            if ("PRI".equalsIgnoreCase(value.getColumnKey())) {
                hasPk = true;
                break;
            }
        }
        map.put("hasPk", hasPk);
        return map;
    }

    private static Connection getConnection(String url, String user, String password) {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            throw new AppException(ErrorCode.SYSTEM_ERROR, e, StringUtil.getUUID());
        }
        return connection;
    }
}
